package org.skyui.views;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Transient;

import org.skyui.beans.AbstractBean;

import org.skyui.beans.AbstractBean.SelectableValue;
import org.skyui.core.ControlledBeanItem;
import org.skyui.core.UIApplication;

import com.baycrypt.server.beans.devices.DeviceBean;
import com.baycrypt.vaadinui.controllers.DeviceLockController;
import com.baycrypt.vaadinui.controllers.GetDeviceInfoController;
import com.baycrypt.vaadinui.controllers.RotateKeyController;
import com.vaadin.Application;
import com.vaadin.data.Property;
import com.vaadin.data.Validator;
import com.vaadin.data.util.BeanItem;
import com.vaadin.terminal.StreamResource;
import com.vaadin.terminal.StreamResource.StreamSource;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.DateField;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Field;
import com.vaadin.ui.Form;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Link;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Upload;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Upload.FailedEvent;
import com.vaadin.ui.Upload.StartedListener;
import com.vaadin.ui.Upload.SucceededListener;

public abstract class AbstractBeanView extends Form implements ClickListener,
	UIView, Validator {

    private static final long serialVersionUID = 4900252424244116186L;
    private Button action = new Button("Save", (ClickListener) this);
    private Button cancel = new Button("Cancel", (ClickListener) this);

    private String passwordField1ID;
    private String passwordField2ID;

    private String name;

    public String getName() {
	return this.name;
    }

    public void setName(String name) {
	this.name = name;
    }

    private String explanation;

    public String getExplanation() {
	return this.explanation;
    }

    public void setExplanation(String explanation) {
	this.explanation = explanation;
    }

    public void setActionButtonName(String actionButtonName) {
	this.action.setCaption(actionButtonName);
    }

    protected AbstractBeansTableView parentTab;

    public void setParentTab(AbstractBeansTableView parentTab) {
	this.parentTab = parentTab;
    }

    AbstractBean bean;
    BeanItem<? extends AbstractBean> item;
    Application application;

    static class CheckBoxListener implements com.vaadin.ui.Component.Listener {

	CheckBox checkBox;
	Component[] positiveControls;
	Component[] negativeControls;

	private static final long serialVersionUID = 818461997163922385L;

	public CheckBoxListener(CheckBox checkBox,
		Component[] positiveControls, Component[] negativeControls) {
	    this.checkBox = checkBox;
	    this.positiveControls = positiveControls;
	    this.negativeControls = negativeControls;
	}

	protected void setEnabled(Component[] components, boolean enabled) {
	    if (components == null)
		return;
	    for (Component c : components) {
		if (c instanceof DateField) {
		    c.setReadOnly(!enabled);

		    continue;
		}
		c.setEnabled(enabled);
		if (c instanceof CheckBox) {
		    ValueChangeEvent event = new ValueChangeEvent((CheckBox) c);
		    ((CheckBox) c).valueChange(event);
		}

	    }
	}

	public void componentEvent(Event event) {
	    if (event.getComponent() != this.checkBox)
		return;
	    if (!this.checkBox.isEnabled()) {
		setEnabled(this.positiveControls, false);
		setEnabled(this.negativeControls, false);
	    } else if (((Boolean) this.checkBox.getValue()).booleanValue()) {
		setEnabled(this.positiveControls, true);
		setEnabled(this.negativeControls, false);
	    } else {
		setEnabled(this.positiveControls, false);
		setEnabled(this.negativeControls, true);
	    }
	}

	public void buttonClick(ClickEvent event) {
	    componentEvent(event);
	}

    }

    public AbstractBeanView(String name, String explanation, AbstractBean bean,
	    Application application) {

	this.name = name;
	this.explanation = explanation;
	this.bean = bean;
	this.application = application;
	this.setFormFieldFactory(new FieldFactoryImpl());
	this.passwordField1ID = bean.getPasswordField1ID();
	this.passwordField2ID = bean.getPasswordField2ID();
	refresh();
	inited = true;
    }

    private static class UploadHandler implements Upload.SucceededListener,
	    Upload.FailedListener, Upload.Receiver {

	private static final long serialVersionUID = -5338068601160935994L;
	private AbstractBeanView beanview;
	private String setMethod;

	private UploadHandler(AbstractBeanView beanview, String setMethod) {
	    this.beanview = beanview;
	    this.setMethod = setMethod;
	}

	private ByteArrayOutputStream bos;

	// Callback method to begin receiving the upload.
	public OutputStream receiveUpload(String filename, String MIMEType) {
	    bos = new ByteArrayOutputStream();
	    return bos; // Return the output stream to write to
	}

	private static Class byteArrayClass = new byte[0].getClass();

	public void uploadSucceeded(Upload.SucceededEvent event) {
	    Method method;
	    try {
		method = this.beanview.bean.getClass().getMethod(setMethod,
			byteArrayClass);
		method.invoke(this.beanview.bean, bos.toByteArray());
		this.beanview.bean.persist();
		this.beanview.refresh();
	    } catch (Exception e) {
		e.printStackTrace();
	    }

	}

	@Override
	public void uploadFailed(FailedEvent event) {
	    // TODO Auto-generated method stub

	}
    }

    Embedded embedded;

    @Transient
    boolean inited;

    List<Component> imageComponents = new ArrayList<Component>();

    public void refresh() {
	this.removeAllActionHandlers();
	this.removeAllProperties();
	this.bean.refresh();
	this.item = new ControlledBeanItem(this.bean);
	this.application = this.application;
	this.setImmediate(true);
	this.setFormDataSource(this.item, null);
	setVisibleItemProperties(this.item.getBean().getOrder());

	setCheckBoxEnableDisableListeners();
	setSelectablesEnableDisableListeners();

	if (this.passwordField1ID != null && this.passwordField2ID != null) {
	    Field field = this.getField(this.passwordField1ID);
	    if (field != null) {
		((TextField) field).setSecret(true);
		field = this.getField(this.passwordField2ID);
		field.addValidator(this);
		((TextField) field).setSecret(true);
	    }
	}

	setWriteThrough(false);

	HorizontalLayout footer = new HorizontalLayout();
	footer.setMargin(true);
	footer.setSpacing(true);
	footer.addComponent(this.action);
	footer.addComponent(this.cancel);
	this.action.setVisible(true);
	this.cancel.setVisible(true);
	footer.setVisible(true);

	if ((this.item.getBean() instanceof StreamResource.StreamSource && !(this.item
		.getBean().isTemplate()))) {

	    // place download before footer
	    VerticalLayout vl = new VerticalLayout();
	    vl.setMargin(true);
	    HorizontalLayout hl = new HorizontalLayout();
	    hl.setMargin(true);
	    hl.addComponent(new Link(this.item.getBean()
		    .getDownloadLinkDescription(), new StreamResource(
		    (StreamSource) this.item.getBean(), this.item.getBean()
			    .getDownloadFileName(), this.application)));

	    vl.addComponent(hl);
	    vl.addComponent(footer);
	    setFooter(vl);
	} else {
	    setFooter(footer);
	}

	List<String[]> imageMaps = this.bean.getImageMaps();
	Iterator<Component> componentIterator = this.getLayout()
		.getComponentIterator();

	int counter = 0;

	if (imageMaps != null && imageMaps.size() != 0) {
	    for (Component c : imageComponents) {
		this.getLayout().removeComponent(c);
	    }
	    imageComponents.clear();
	    for (String[] map : imageMaps) {
		if (map[2] != null) {
		    UploadHandler hndlr = new UploadHandler(this, map[2]);
		    Upload up = new Upload("", hndlr);
		    up.setImmediate(true);
		    up.setButtonCaption("Upload new " + map[0]);
		    up.addListener((Upload.SucceededListener) hndlr);
		    up.addListener((Upload.FailedListener) hndlr);
		    ((FormLayout) this.getLayout()).addComponent(up, 0);
		    imageComponents.add(up);
		}
		if (map[1] != null) {
		    StreamSource source = new ByteArrayStreamSource(this.bean,
			    map[1]);
		    if (source.getStream() != null) {
			Embedded embedded = new Embedded(
				map[0],
				new StreamResource(
					source,
					counter++
						+ com.baycrypt.server.utils.BayCryptUtils
							.randomPositiveLongString()
						+ ".jpg", UIApplication
						.getInstance()));
			embedded.setHeight("150px");
			((FormLayout) this.getLayout()).addComponent(embedded,
				0);
			imageComponents.add(embedded);
		    }
		}

	    }
	}
	;
	setSingleItemCombos();
	this.requestRepaint();

    }

    private void setSingleItemCombos() {
	Iterator<Component> it = this.getLayout().getComponentIterator();
	while (it.hasNext()) {
	    Component combo = it.next();
	    if (combo instanceof ComboBox) {
		ComboBox c = (ComboBox) combo;
		if (c.getItemIds().size() == 1) {
		    c.setValue(c.getItemIds().iterator().next());
		}
	    }
	}

    }

    private void setCheckBoxEnableDisableListeners() {
	List<SelectableValue> cbc = this.item.getBean().getCheckBoxControls();

	if (cbc != null) {
	    for (SelectableValue sv : cbc) {
		CheckBox cb = (CheckBox) this.getField(sv.value);
		CheckBoxListener listener = new CheckBoxListener(cb,
			getComponents(sv.positiveControls),
			getComponents(sv.negativeControls));
		cb.addListener(listener);
		ValueChangeEvent event = new ValueChangeEvent(cb);
		(cb).valueChange(event);
	    }
	}
    }

    private Set<Field> disabledRequireds = new HashSet<Field>();

    private ValueChangeListener getSelectableValueChangeListener(
	    final SelectableValue option) {
	return new Property.ValueChangeListener() {

	    @Override
	    public void valueChange(Property.ValueChangeEvent event) {

		if (option.value.equals(event.getProperty().getValue())) {
		    if (option.positiveControls != null) {
			for (String pc : option.positiveControls) {
			    Field f = AbstractBeanView.this.getField(pc);
			    f.setEnabled(true);
			    if (disabledRequireds.contains(f)) {
				f.setRequired(true);
				disabledRequireds.remove(f);
			    }
			}
		    }
		    ;
		    if (option.negativeControls != null) {
			for (String nc : option.negativeControls) {
			    Field f = AbstractBeanView.this.getField(nc);
			    f.setEnabled(false);

			    if (f.isRequired()) {
				f.setRequired(false);
				disabledRequireds.add(f);
			    }

			}

		    }
		}

	    }
	};
    }

    private void setSelectablesEnableDisableListeners() {

	SelectableValue[] options;

	Map<String, SelectableValue[]> selectables = this.item.getBean()
		.getSelectablesMap();
	if (selectables != null) {
	    for (String propertyId : selectables.keySet()) {
		AbstractSelect select = (AbstractSelect) this
			.getField(propertyId);

		for (final SelectableValue option : selectables.get(propertyId)) {
		    if (option.positiveControls != null
			    || option.negativeControls != null) {
			select.addListener(getSelectableValueChangeListener(option));
		    }
		}
		select.valueChange(new ValueChangeEvent(select));
	    }
	}

    }

    Component[] getComponents(String[] names) {
	if (names == null)
	    return null;
	Field[] components = new Field[names.length];
	for (int i = 0; i < names.length; i++) {
	    components[i] = this.getField(names[i]);
	}
	return components;
    }

    public void buttonClick(ClickEvent event) {
	Button source = event.getButton();
	if (source == this.action) {

	    if (!isValid()) {
		return;
	    }
	    commit();
	    actionAfterValidation();

	} else if (source == this.cancel) {
	    discard();
	    actionAfterCancel();
	}
    }

    protected abstract void actionAfterValidation();

    protected abstract void actionAfterCancel();

    boolean validated = false;

    public boolean isValid(Object value) {
	if (this.validated)
	    return true;
	this.validated = value.equals(this.getField(this.passwordField1ID)
		.getValue());
	return this.validated;
    }

    public void validate(Object value) throws InvalidValueException {
	if (this.passwordField1ID == null)
	    return;
	if (!isValid(value))
	    throw new Validator.InvalidValueException("Passwords dont match");
	// TODO Auto-generated method stub

    }
}
